This ZIP describes a mechanism for the organization of coin-weighted election.
A person or organization, referred as the Election Board (EB) decides to request the opinion of the zcash holders (ZH) on a particular topic. The EB defines the parameters of the referendum:
They publically ask the ZH to participate by sending them ballots. They verify and tally the votes following the aggregation method (typicall linear, but it could be quadratic or something else). Then they publish the outcome.
The final result of the election is a list of valid ballots that have:
The content of the message depends on the type of referendum. The acceptable options should be defined by EB. For example, for a yes/no question, it could simply be a value of 0 or 1.
The EB could decide to accept write-in votes too. The format of the message is not part of this ZIP.
The value of the ballot MUST be an amount of ZEC that the ZH can spend at the snapshot height.
The ZH is not obligated to vote with all the ZEC that they have. But it MUST be impossible to use the same coins to vote twice on the same election.
It MUST be possible to use the same ZEC on DIFFERENT elections.
See Privacy section on important caveats regarding concurrent voting.
This ZIP can be used to perform air-drops. An organization that wants to give their tokens/coins based on an amount of ZEC can organize an election. The ballots contain the ZH’s address where they want to receive the airdrop.
In this case, the ZH sends an empty message because the ballot value is all that matters.
The Election Board (EB) is the entity in charge of organizing the election (E).
Anyone who has spending authority of ZEC (either transparent or shielded) can participate.
Notes:
If the coins used for voting for a given election are not moved before voting for another election, it is possible to link the votes.
The total value is also revealed since it is the weight of the vote.
To mitigate this issue, the user SHOULD be offered to move their coins after a vote. However, if multiple elections are carried out concurrently with similar snapshot heights, it will be impossible for a Voter to participate in both without linking their votes.
A possible solution is suggested in Air drops, Proof-of-Balance, and Stake-weighted Polling
Alternatively, homomorphic encryption could be used in order to tally the votes without decrypting them individually. However, this would be less flexible than the above.
The election E is carried in several sequential phases.
The message part of a Ballot is encrypted with the V key. The decryption key is sent to the EB after the lockdown period. The EB validates the ballot coin value and returns a receipt. The Receipt contains a signature from the EB.
The lockdown period is optional but RECOMMENDED.
It gives time for the Voters to check that their ballot is recorded by the EB publicly and that no additional ballots are added after their opening.
If a V does not see their ballot on the list, they MUST contact the EB and resolve the dispute. Their receipt has the EB signature and proves the integrity of the ballot. Failure to recognize a valid ballot is a serious breach of trust.
After the lockdown period, V MUST send their decryption key to the EB.
EB decrypts the vote and updates the tally.
Finally EB publishes the results for everyone to see.
The encrypted ballots and their decryption keys are published for verification.
The communication uses binary messages in protobuf format.
Example:
url = "https://election.methyl.cc"
name = "Test Election 2024"
start_height = 2397000
snapshot_height = 2462430
lockdown_height = 2460000
closing_height = 2460000
msg_size = 64
public= "788ff50468e2c095d97f8249e27eef3afe8a07712988a475583eb89494d64abe"
cmx_root = "063121a1fed23f154e18d9dfc70a13867f295207fd3c59d0e3f86615b857d332"
nf_root = "5341fe36634580cdf8862a106dab2cf7bd39f67d171a2877995babb435a5ee0e"
Only notes received and unspent between the start_height and the snapshot height are eligible.
The start_height defaults to the Sapling activation height.
In addition, the EB MUST define a message in protobuf format. The message MUST have a fixed size.
public is defined in the section Message Encryption
Example:
message BallotContent {
uint8 candidate = 1;
}
From the E definition, the E domain is calculated as
domain = Blake2b-256(b"Zcash_WCV_domain", E)
The E domain is included in the ballot and ensures that ballots cannot be replayed.
syntax = "proto3";
package proof_balance;
// Protobuf does not support fixed size arrays
// This type MUST be 32 byte-long
message b256 {
bytes b = 1;
}
// A ballot is made of 2 parts,
// the data part is non malleable and is used to calculate the sighash
// the witness part has the signatures and the zkp
message Ballot {
BallotData data = 1;
Witness witness = 2;
}
message Header {
uint32 version = 1; // version = 1
b256 domain = 2; // Election Domain
}
message BallotData {
Header header = 1;
TransparentData tdata = 2; // transparent inputs
ShieldedData zdata = 3; // sapling inputs
ShieldedData odata = 4; // orchard inputs
EncMessage enc_message = 5; // encrypted vote
}
message EncMessage {
bytes epk = 1;
bytes data = 2;
bytes tag = 3;
}
message Witness {
TransparentWitness twitness = 1; // transparent sigs
SaplingWitness zwitness = 2; // sapling sigs & zkp
OrchardWitness owitness = 3; // orchard sigs & zkp
}
// Transparent Input, i.e UTXO
message TxIn {
b256 txid = 1; // prev txout
uint32 vout = 2; // index of the output
}
message TransparentData {
repeated TxIn txin = 1;
}
// See section 7.3 and 7.5 of [protocol doc]
message Spend {
b256 commitment_value = 1;
b256 nullifier = 2;
b256 rk = 3;
}
message ShieldedData {
// b256 anchor = 1; // MUST be the anchor at snapshot height
repeated Spend spends = 2;
uint64 valuebalance = 3;
}
message Signature {
bytes b = 1; // 64 bytes, r, s
}
message TransparentProof {
b256 pk = 1;
Signature signature = 2;
}
message TransparentWitness {
repeated TransparentProof proofs = 1;
}
message SaplingSpendProof {
bytes zkproof = 1; // 192 bytes
Signature signature = 2;
}
message SaplingWitness {
repeated SaplingSpendProof proofs = 1;
Signature binding_signature = 2;
}
message OrchardWitness {
repeated Signature signatures = 1;
bytes zkproof = 2; // 2720 + 2272 * nOrchard
Signature binding_signature = 3;
}
message ElectionId {
string name = 1;
}
message ElectionInfo {
string url = 1;
string name = 2;
uint32 start_height = 3;
uint32 snapshot_height = 4;
uint32 lockdown_height = 5;
uint32 closing_height = 6;
string pk = 7;
uint32 msg_size = 8;
bytes cmx_root = 9;
bytes nf_root = 10;
}
message ProofDomain {
bytes domain = 1;
}
message Proof {
bytes cv = 1;
bytes nullifier = 2;
bytes rk = 3;
bytes proof = 4;
}
For the signatures, the sighash is the blake2b-256
hash of the serialization of the ballot data (in protobuf 3)
with personalization string Zcash_WCV___Hash.
hash = Blake2b-256("Zcash_WCVSigHash", BallotData).
The EB MUST sign the ballot sighash with a ed25519 signing key and return
a receipt as a bech32m encoded signature with hrp pob-receipt-
The Voter MUST add a message in the format specified by the EB. The message is encrypted into EncMessage.
Rationale: This design allows the use of ed25519 SSH keys for both Voter and EB.
The Voter SHOULD not reuse the signing key!
The POC is for a modified version specific to Orchard that uses nullifiers scoped by elections.
The EB (Election Board) generates an openssh ed25519 signing key
openssl genpkey -algorithm ed25519 -out poller.pem
The key MAY be reused. It is used for signing ballot receipts. It is RECOMMENDED to use a different key per election.
The EB chooses a event properties
url = "https://election.methyl.cc"
name = "Test Election 2024"
start_height = 2397000
snapshot_height = 2462430
lockdown_height = 2460000
closing_height = 2460000
msg_size = 64
public= "788ff50468e2c095d97f8249e27eef3afe8a07712988a475583eb89494d64abe"
cmx_root = "063121a1fed23f154e18d9dfc70a13867f295207fd3c59d0e3f86615b857d332"
nf_root = "5341fe36634580cdf8862a106dab2cf7bd39f67d171a2877995babb435a5ee0e"
The public key MUST match the EB signing key.
The cmx_root and nf_root are OPTIONAL but if present
MUST match the hashes calculated by building the reference data.
If the snapshot height is not in the past, it is impossible to calculate the tree roots.
The EB publishes the election.toml file so that voters can download it.
Once the snapshot height is reached and before the lockdown height, voters can submit their vote.
They have to follow the steps:
zec.db from Ywallet. The vote does not
have to use this wallet app as long as it has the note data:
>> load-identity voter.pem
c76a89174ed64479c5f567b955ce27ce998568e72a044de80b0ebe748d826b3c
>> download https://lwd1.zcash-infra.com:9067
>> build-reference-data
Root cmxs: 5de18172c1c9d2a526a9c73067e75dfbe1ac4e774e1bfec9a31ee178db98a635
Root nfs: 12d4776996da7929255bccfcb21c99853d840c4008da547b113c4c857eec3d29
>> create-proof pob.bin 1 "Vote for BOB!"
5c70068567d0b8d52e4dcfc395a67dc83fd6bdf9c65a835b41d2bfeb776480fb
cmx anchor: 5de18172c1c9d2a526a9c73067e75dfbe1ac4e774e1bfec9a31ee178db98a635
nf anchor: 12d4776996da7929255bccfcb21c99853d840c4008da547b113c4c857eec3d29
The EB collects votes and verifies the validity of the ZKP, spending signatures and binding signature. It also checks that the root hashes match and that the nullifiers are not used.
If everything is fine, the EB returns a receipt that is a signature on the sighash of the ballot.
>> load-identity poller.pem
3f8a41af400aa0197594a7b5c2e21bb6307753840a8513b28b816aad43fb30c0
>> init-db
>> verify-proof pob.bin
ZK Proof verified!
Spending Signature verified!
Binding Signature verified!
Verified 0.05000000 ZEC
Receipt: pob-receipt-1djsvezm3wep2ganwd4upzawxsgz60hq2e86cqkv4djmusut4fjzzvzu2ut5z3uda4eedvff6anlmzz3k48d7ugxsrs0gxfn36nscvqqzn4js8
At this point, the ballot is verified and its amount can be accounted for but the vote payload is not revealed.
The EB publishes the ballot hashes and receipts on their website.
Voters uploads their signing key to the EB.
>> load-identity voter.pem
c76a89174ed64479c5f567b955ce27ce998568e72a044de80b0ebe748d826b3c
>> open-proof pob.bin
5c70068567d0b8d52e4dcfc395a67dc83fd6bdf9c65a835b41d2bfeb776480fb
Decrypted message: Vote for BOB!
The EB counts the votes and publishes the results
The EB publishes all ballots and their opening keys for verification by auditors.